﻿#pragma once

#include <string>

#include <fast_io.h>
#include <fast_io_device.h>

#define Helium_LOGGER_FLUSH

namespace Helium {

enum class LogGrade : uint8_t {
	trace = 0ui8,
	debug,
	info,
	warning,
	error,
	fatal,
	chat_log
};

class Logger final {
public:
#if __has_cpp_attribute(__gnu__::__always_inline__)
	[[__gnu__::__always_inline__]]
#elif __has_cpp_attribute(msvc::forceinline)
	[[msvc::forceinline]]
#endif
	inline static constexpr std::u8string_view GetGradeName(LogGrade grade) noexcept {
		using namespace std::string_view_literals;
		switch (grade) {
		case LogGrade::trace:
			return u8"[TRACE] ";
		case LogGrade::debug:
			return u8"[DEBUG] ";
		case LogGrade::info:
			return u8"[INFO] ";
		case LogGrade::warning:
			return u8"[WARN] ";
		case LogGrade::error:
			return u8"[ERROR] ";
		case LogGrade::fatal:
			return u8"[FATAL] ";
		case LogGrade::chat_log:
			return u8"[CHAT] ";
		}
		std::unreachable();
		return std::u8string_view{};
	}
	static void Enable() noexcept;
	static void Disable() noexcept;

#ifdef Helium_LOGGER_FLUSH
	static void Flush() noexcept;
#endif  // Helium_LOGGER_FLUSH

	template <LogGrade grade = LogGrade::info, bool wrap = true, bool log_to_file = true,
			  bool log_to_dbg =
#ifdef _DEBUG
				  true,
#else
				  false,
#endif  // _DEBUG
			  typename... Args>
	static void Log(Args&&... args) noexcept {
		static_assert(log_to_file || log_to_dbg, "Logs output to at least one location");
		if (!LoggerHasInit) [[unlikely]]
			fast_io::fast_terminate();
		constexpr auto GradeName{GetGradeName(grade)};
		if constexpr (log_to_file && log_to_dbg) {
			if constexpr (wrap) {
				fast_io::io::println(log_file, fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
				fast_io::io::println(fast_io::u8dbg(), fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			} else {
				fast_io::io::print(log_file, fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
				fast_io::io::print(fast_io::u8dbg(), fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			}
		} else if constexpr (log_to_file) {
			if constexpr (wrap) {
				fast_io::io::println(log_file, fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			} else {
				fast_io::io::print(log_file, fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			}
		} else if constexpr (log_to_dbg) {
			if constexpr (wrap) {
				fast_io::io::println(fast_io::u8dbg(), fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			} else {
				fast_io::io::print(fast_io::u8dbg(), fast_io::mnp::left(local(fast_io::posix_clock_gettime(fast_io::posix_clock_id::realtime)), 34), GradeName, args...);
			}
		}
#ifdef Helium_LOGGER_FLUSH
		can_refresh = true;
#endif  // Helium_LOGGER_FLUSH
	}

public:
	static fast_io::u8obuf_file_mutex log_file;  // Remember to delete

#ifdef Helium_LOGGER_FLUSH
private:
	static bool can_refresh;
#endif  // Helium_LOGGER_FLUSH
};

}  // namespace Helium